﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace tomssoftware.Html
{
    public class HtmlToText : IDisposable
    {
        private static Dictionary<string, string> namedChars;
        private static string[] map = new string[] {"nbsp"," ","amp","&","quot","\"","lt","<","gt",">","le","<=","ge",">=","minus","-",
            "times","x","divide","÷","plusmn","±","ne","!=","frasl","/","deg","°","euro","€","radic","√","bull","•","sect","§","copy","(c)","reg","(r)",
            "uuml","ü","ouml","ö","auml","ä","Uuml","Ü","Ouml","Ö","Auml","Ä","szlig","ß","circ","^","title","~",
            "trade","tm", "darr","|","uarr","|","brvbar","|","ndash","_","mdash","-"};

        static HtmlToText()
        {
            int n = map.Length / 2;
            namedChars = new Dictionary<string, string>(n);
            for (int i = 0; i < n; i++)
            {
                string k = map[i * 2];
                string v = map[i * 2 + 1];
                namedChars.Add(k, v);
            }
        }

        private bool includeImageText = true;
        private HtmlParser parser;
        private MemoryStream ms;

        public HtmlToText(string html, bool includeImageText)
        {
            this.includeImageText = includeImageText;
            ms = new MemoryStream(Encoding.Default.GetBytes(html));
            ms.Position = 0;
            parser = new HtmlParser(ms);
            Body = GetBody();
        }

        public string Body { get; private set; }

        private string GetBody()
        {
            StringBuilder sb = new StringBuilder();
            if (parser.Read())
            //if (ReadToBody())
            {
                bool exit = false;
                do
                {
                    if (parser.IsElement)
                    {
                        switch (parser.ElementId)
                        {
                            case ElementId.body:
                                sb.Length = 0;
                                break;

                            //case ElementId.meta:
                            case ElementId.head:
                            case ElementId.Object:
                            case ElementId.script:
                            case ElementId.noscript:
                            case ElementId.style:
                                if (parser.Type == HtmlType.OpenElement) parser.SkipToEndElement();
                                break;

                            case ElementId.img: sb.Append(GetImageLink()); break;
                            case ElementId.br: sb.AppendLine(); break;
                            case ElementId.li:
                                sb.Append(parser.Type == HtmlType.Element ? " •\n" : " • ");
                                break;

                            case ElementId.div:
                                if (parser.Type == HtmlType.Element) sb.AppendLine();
                                break;

                        }
                    }
                    else if (parser.Type == HtmlType.CloseElement)
                    {
                        switch (parser.ElementId)
                        {
                            case ElementId.body:
                                exit = true;
                                break;

                            case ElementId.p:
                            case ElementId.table:
                            case ElementId.tr:
                            case ElementId.li: AddLineFeed(sb); break;
                            case ElementId.div: sb.AppendLine(); break;
                        }
                    }
                    else if (IsContent()) sb.Append(ToAscii(parser.Content));
                } while (!exit && parser.Read());
            }

            return sb.ToString();
        }

        private void AddLineFeed(StringBuilder sb)
        {
            if (sb.Length == 0 || sb[sb.Length - 1] != '\n') sb.AppendLine();
        }

        private bool IsBr()
        {
            return (parser.IsElement && string.Compare("br", parser.ElementName, true) == 0);
        }

        private string ToAscii(string html)
        {
            html = CleanupWhiteSpaces(html);
            int index = html.IndexOf('&');
            if (index < 0) return html;

            int len = html.Length;
            StringBuilder sb = new StringBuilder(len);
            int lastIndex = 0;
            while (index >= 0)
            {
                index++;
                int endIndex = html.IndexOf(';', index);
                if (endIndex < 0) endIndex = len - 1;
                string code = html.Substring(index, endIndex - index);
                sb.Append(html.Substring(lastIndex, index - lastIndex - 1));
                sb.Append(ConvertToAscii(code));
                lastIndex = endIndex + 1;
                index = html.IndexOf('&', lastIndex);
            }
            sb.Append(html.Substring(lastIndex));
            return sb.ToString();
        }

        private string CleanupWhiteSpaces(string html)
        {
            StringBuilder sb = new StringBuilder(html.Length);
            bool skip = false;
            foreach (char c in html)
            {
                if (IsWhiteChar(c))
                {
                    if (!skip) sb.Append(' ');
                    skip=true;
                }
                else
                {
                    skip = false;
                    sb.Append(c);
                }
            }
            if (sb.Length == 1 && sb[0] == ' ') return string.Empty;
            return sb.ToString();
        }

        private bool IsWhiteChar(char c)
        {
            switch (c)
            {
                case '\n':
                case '\r':
                case '\t':
                case ' ': return true;
                default: return false;
            }
        }

        private string ConvertToAscii(string code)
        {
            if (code.Length > 0 && code[0] == '#')
            {
                return DecodeBin(code);
            }
            string a;
            if (namedChars.TryGetValue(code, out a)) return a;
            return "";
        }

        private string DecodeBin(string code)
        {
            string c = code.Substring(1);
            try
            {
                int v = Convert.ToInt32(c);
                return Convert.ToChar(v).ToString();
            }
            catch
            {
                return "";
            }
        }


        private bool IsContent()
        {
            return parser.Type == HtmlType.Content;
        }

        private string GetImageLink()
        {
            if (!includeImageText) return string.Empty;
            var attribs = parser.Attributes;
            var attr = attribs.FirstOrDefault(a => string.Compare("alt", a.Name, true) == 0);
            string link = attr != null ? "[img: " + attr.UnqotedValue + "]" : "[img ?]";
            return link;
        }

        private bool ReadToBody()
        {
            while (parser.Read())
            {
                if (parser.ElementId == ElementId.body) return true;
            }

            return false;
        }



        public void Dispose()
        {
            parser.Dispose();
            ms.Dispose();
            GC.SuppressFinalize(this);
        }

        ~HtmlToText()
        {
            Dispose();
        }
    }
}
